package no.ffi.gismoidm.certstore;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509CRL;
import java.util.Date;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.x500.X500Principal;
import no.ffi.utils.Base64;
import no.ffi.utils.StringSplitter;
import sun.security.provider.X509Factory;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId;
import sun.security.x509.CertificateAlgorithmId;
import sun.security.x509.CertificateExtensions;
import sun.security.x509.CertificateIssuerName;
import sun.security.x509.CertificateSerialNumber;
import sun.security.x509.CertificateSubjectName;
import sun.security.x509.CertificateValidity;
import sun.security.x509.CertificateVersion;
import sun.security.x509.CertificateX509Key;
import sun.security.x509.DNSName;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.SubjectAlternativeNameExtension;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;

/**
 *
 * @author anders
 */
public class CertificateFileOperations {
    public static X509Certificate loadPemCert(String pemFilename) throws Exception {
        X509Certificate x509cert=null;
        boolean started = false;
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        FileReader fr = new FileReader(pemFilename);
        // Now, remove the text before "-----BEGIN CERTIFICATE-------"
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        BufferedReader br = new BufferedReader(fr);
        String s = br.readLine();
        while (s!=null) {
            if (s.startsWith("-----BEGIN CERTIFICATE---")) started = true;
            if (started) { bos.write(s.getBytes()); bos.write('\n');}
            s = br.readLine();
        }
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());

        x509cert = (X509Certificate)cf.generateCertificate(bis);
        return x509cert;
    }
    
    public static X509CRL loadPemCRL(String path) throws Exception {
        InputStream inStream = null;
        try {
            inStream = new FileInputStream(path);
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            return (X509CRL)cf.generateCRL(inStream);
        } finally {
            if (inStream != null) {
                inStream.close();
            }
        }
    }
    
    public static X509Certificate loadDERCert(InputStream iStream) throws Exception {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate x509cert=null;
        x509cert = (X509Certificate)cf.generateCertificate(iStream);
        return x509cert;
    }
    
    public static X509Certificate loadPkcsCert(String pkcsfile, String filepass) throws Exception {
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(new FileInputStream(pkcsfile),filepass.toCharArray());
        Enumeration en = ks.aliases();
        if (en.hasMoreElements()) {
            String al = (String)en.nextElement();
            X509Certificate cert = (X509Certificate)ks.getCertificate(al);
            return cert;
        }
        return null;
    }

    public static PrivateKey loadPkcsKey(String pkcsfile,String filepass, String keypass) throws Exception {
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(new FileInputStream(pkcsfile),filepass.toCharArray());
        Enumeration en = ks.aliases();
        if (en.hasMoreElements()) {
            String al = (String)en.nextElement();
            PrivateKey pkey = (PrivateKey)ks.getKey(al, keypass.toCharArray());
            return pkey;
        }
        return null;
    }
    
    public static X509Certificate loadDERCert(String derFilename) throws Exception {
        return loadDERCert(new FileInputStream(derFilename));
    }
    
    public static X509Certificate loadAnyCert(String filename) throws Exception {
        try { // try first to load PEM format, then DER
                return CertificateFileOperations.loadPemCert(filename);
            } catch (java.security.cert.CertificateException ce) {
                return CertificateFileOperations.loadDERCert(filename);
            }
    }
    
    public static String getX509CertIssuerHash(X509Certificate cert) throws Exception {
            return getX500PrincipalHash(cert.getIssuerX500Principal());
    }
    
    public static String getX509CertSubjectHash(X509Certificate cert) throws Exception {
            return getX500PrincipalHash(cert.getSubjectX500Principal());
    }
    
    public static String getX500PrincipalHash(X500Principal xp) throws Exception {
            byte[] bytes = xp.getEncoded();
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] thedigest = md.digest(bytes);          
            return byteToHex(reverse4(thedigest));
    }
    

    private static String byteToHex(byte[] b) {
        String translate = "0123456789abcdef";
        StringBuffer result = new StringBuffer();
        for (int i=0; i<b.length; i++) {
            int i1 = b[i] & 0x0F;        // Low nibble
            int i2 = (b[i] & 0xF0) >> 4; // High nibble
            result.append(translate.charAt(i2));
            result.append(translate.charAt(i1));
        }
        return new String(result);
    }
    
    private static byte[] reverse4(byte[] b) {
        if (b.length<4) return null;
        byte[] b2 = new byte[4];
        b2[0] = b[3]; b2[1] = b[2];
        b2[2] = b[1]; b2[3] = b[0];
        return b2;
    }
    
    
    /** 
 * Create a self-signed X.509 Certificate
 * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
 * @param pair the KeyPair
 * @param days how many days from now the Certificate is valid for
 * @param algorithm the signing algorithm, eg "SHA1withRSA"
 */ 
    public static X509Certificate generateCertificate(String dn, PublicKey pubkey, String san, 
            BigInteger serialNo, X509Certificate caCert, PrivateKey caKey, int days)
      throws GeneralSecurityException, IOException, Exception {
      String algorithm = "sha1withrsa";
      X509CertInfo info = new X509CertInfo();
      Date from = new Date();
      Date to = new Date(from.getTime() + days * 86400000l);
      CertificateValidity interval = new CertificateValidity(from, to);
      X500Name issuer = new X500Name(caCert.getSubjectDN().getName()); // Do better
      X500Name owner =  new X500Name(dn);
      SubjectAlternativeNameExtension subjAltName = 
              new SubjectAlternativeNameExtension(
                      (new GeneralNames()).add
                        (new GeneralName(new DNSName(san))));

      info.set(X509CertInfo.VALIDITY, interval);
      info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
      info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(serialNo));
      info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
      info.set(X509CertInfo.ISSUER, new CertificateIssuerName(issuer));
      CertificateExtensions certExt = new CertificateExtensions();
      certExt.set("SubjectAlternateName", subjAltName);
      info.set(X509CertInfo.EXTENSIONS, certExt);      
      info.set(X509CertInfo.KEY, new CertificateX509Key(pubkey));
      AlgorithmId algo = new AlgorithmId(AlgorithmId.sha1WithRSAEncryption_oid);
      info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));

      // Sign the cert to identify the algorithm that's used. Use a dummy key first
      KeyPairGenerator kpg = KeyPairGenerator.getInstance("rsa");
      kpg.initialize(2048);
      KeyPair kp = kpg.generateKeyPair();
      X509CertImpl cert = new X509CertImpl(info);
      cert.sign(kp.getPrivate(), algorithm);
      cert.verify(kp.getPublic());

      // Update the algorith, and resign.
      algo = (AlgorithmId)cert.get(X509CertImpl.SIG_ALG);
      info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
      cert = new X509CertImpl(info);
      cert.sign(caKey, algorithm);
      cert.verify(caCert.getPublicKey());
      System.out.println("Cert is: " + cert);
      
      return cert;
    }   
    
    public static boolean writeCertificateAsPem(X509Certificate cert, String path) {
        String pemAsOneLine;
        try {
            pemAsOneLine = Base64.encodeToString(cert.getEncoded(), false);
        } catch (CertificateEncodingException ex) {
            return false;
        }
        
        StringBuilder sb = new StringBuilder();
        sb.append(X509Factory.BEGIN_CERT);
        for (String pemLine : StringSplitter.splitToFixedLength(pemAsOneLine, 64)) {
            sb.append(pemLine);
            sb.append("\n");
        }
        sb.append(X509Factory.END_CERT);
        
        try {
            BufferedWriter bw = new BufferedWriter(new FileWriter(path));
            bw.write(sb.toString());
        } catch (Exception e) {
            return false;
        } 
        return true;
    }
    
    public static void saveCertificate(X509Certificate cert, String certDir,String filenameStem) throws Exception {
        X509Certificate check;
        String pathname;
        if (!certDir.endsWith("/")) certDir = certDir + "/";
        char suffix = '0';
        while (true) {
            pathname = certDir + filenameStem + "." + suffix;
            File f = new File(pathname);
            if (!f.exists()) break; // Move on and write
            suffix++;
        }
        FileOutputStream fos = new FileOutputStream(pathname);
        fos.write(cert.getEncoded());
    }
    
//    public static void main(String[] args) throws Exception {
//        CertificateExtensions certExt = new CertificateExtensions();
//        int[] oid = new int[]{2,5,29,17};
//        String s = certExt.getNameByOid(new ObjectIdentifier(oid));
//        System.out.println(s);
//
//    }

}
